import fs from 'fs-extra';
import yaml from 'yaml'
import { YamlClassID } from './YamlClassID'
import { TUnityType, UnityEngine, UnityObject } from '../typings/unity.js'
import { Undef } from '../typings/index.js';

export const enum EGUID {
    Image = 'fe87c0e1cc204ed48ad3b37840f39efc',
    RawImage = '1344c3c82d62a2a41a3576d8abb8e3ea',
    Toggle = '9085046f02f69544eb97fd06b6048fe2',
    Outline = 'e19747de3f5aca642ab2be37e372fb86',
    Shadow = 'cfabb0440166ab443bba8876756fdfa9'
}

export interface UnityElement<T = UnityObject> {
    yamlID: YamlClassID;
    fileID: string;
    stripped: boolean;
    type: TUnityType;
    object: T;
    /**for MonoBehaviour, the key is MonoBehaviour_${fileID} */
    components: {
        SpriteRenderer?: UnityElement<UnityEngine.SpriteRenderer>;
        SpriteMask?: UnityElement<UnityEngine.SpriteMask>;
    } & { [type: string]: UnityElement };
    father?: UnityElement;
    children: UnityElement[];
}

export type Prefab = UnityElement[];

export class PrefabParser {
    public static readonly SectionHeader = /--- !u!(\d+) &(-?\d+)( stripped)?/;
    private unityStruct: any = {};

    /**解析Prefab，获得根GameObject为起点的GameObject树 */
    async parse(filePath: string): Promise<Prefab> {
        const elements: Prefab = [];
        const elemMap: {[fileID: string]: UnityElement} = {};

        let fileContent = await fs.readFile(filePath, 'utf-8');
        fileContent = fileContent.replaceAll(/(\r?\n){2,}(?=')/g, '');  // 去掉text末尾换行的情况
        let fileLines = fileContent.split(/[\r\n]+/);
        // 前两行为tag跳过
        let elemContent = '';
        let elem: Undef<UnityElement>;
        for(let i = 2, len = fileLines.length; i < len; i++) {
            const line = fileLines[i];
            const docMatchRst = line.match(PrefabParser.SectionHeader);
            if(docMatchRst) {
                // 匹配到新元素
                if(elem != null) {
                    this.parseUnityElement(elem, elemContent, filePath);
                }
                elem = {} as UnityElement;
                elem.yamlID = Number(docMatchRst[1]);
                elem.fileID = docMatchRst[2];
                elem.stripped = Boolean(docMatchRst[3]);
                elemMap[elem.fileID] = elem;
                elements.push(elem);
                elemContent = '';
            } else {
                elemContent += line + '\n';
            }
        }

        if(elem != null) {
            this.parseUnityElement(elem, elemContent, filePath);
        }

        const childBook: { [fileID: string]: true } = {};
        for (const e of elements) {   
            if (e.type == 'GameObject') {
                e.components = {};
                const components = (e.object as UnityEngine.GameObject).m_Component;
                if (components != null) {
                    for (const c of components) {
                        const comp = elemMap[c.component.fileID.toString()];
                        let key = comp.type;
                        if (comp.type == 'MonoBehaviour') {
                            key += '_' + (comp.object as UnityEngine.MonoBehaviour).m_Script.guid;
                        }
                        e.components[key] = comp;
                    }
                }
            } else if (e.type == 'Transform' || e.type == 'RectTransform') {
                if ((e.object as UnityEngine.Transform).m_GameObject == null) {
                    if (!e.stripped) {
                        console.error('RectTransform without GameObject:', e.fileID);
                    }
                    continue;
                }
                const go = elemMap[(e.object as UnityEngine.Transform).m_GameObject.fileID.toString()];
                const children = (e.object as UnityEngine.Transform).m_Children;
                if (children != null) {
                    e.children = [];
                    for (const c of children) {
                        childBook[c.fileID] = true;
                        let child = elemMap[c.fileID];
                        if (child.type == 'Transform' || child.type == 'RectTransform') {
                            if ((child.object as UnityEngine.Transform).m_GameObject != null) {
                                child = elemMap[(child.object as UnityEngine.Transform).m_GameObject.fileID.toString()];
                            }
                        }
                        if (child != null) {
                            if (child.type == 'GameObject') {
                                e.children.push(child);
                            } else {
                                // console.error('child is not GameObject:', child.fileID);
                            }
                        } else {
                            console.error('could not get child:', c.fileID);
                        }
                    }
                    go.children = e.children;
                }
                const father = (e.object as UnityEngine.Transform).m_Father;
                if (father != null) {
                    e.father = elemMap[father.fileID];
                    go.father = e.father;
                }
            }
        }

        const prefab: Prefab = [];
        for (const e of elements) {
            if (e.type == 'GameObject' && e.father == null) prefab.push(e);
        }

        // 更新unity结构描述
        // for(let i = 0, len = elements.length; i < len; i++) {
        //     let elem = elements[i];
        //     let structMap = this.unityStruct[elem.type];
        //     if(!structMap) {
        //         this.unityStruct[elem.type] = structMap = {};
        //     }
        //     // if (elem.type == 'MonoBehaviour' && elem.fileID == '6860625456729184521') {
        //     //     this.getDesciption(elem.object, structMap);
        //     // }
        // }
        // this.dumpDescribor();

        return prefab;
    }

    private parseUnityElement(elem: UnityElement, elemContent: string, filePath: string): void {
        try {
            let obj = yaml.parse(elemContent, { intAsBigInt: true, toStringDefaults: { singleQuote: true } });
            for(const key in obj) {
                elem.type = key as TUnityType;
                elem.object = obj[key];
                break;
            }
        } catch(e) {
            console.log(elemContent);
            console.error(e);
            throw new Error('YAML parse error: ' + filePath);
        }
        if(!elem.type) {
            throw new Error('Prefab parse error!');
        }
    }

    private getDesciption(obj: any, structMap: any) {
        for(let key in obj) {
            let node = obj[key];
            let nodeType = typeof(node);
            if(!structMap[key]) {
                if(nodeType == 'object') {
                    if(!structMap[key]) {
                        structMap[key] = {};
                    }
                } else {
                    try {
                        structMap[key] = nodeType;
                    } catch(e) {
                        console.error(e);
                        throw new Error('getDesciption error!');
                    }
                }
            } 
            if(nodeType == 'object') {
                this.getDesciption(node, structMap[key]);
            } 
        }
    }

    dumpDescribor() {
        for(let type in this.unityStruct) {
            let lines = this.getDescriptionStr(this.unityStruct[type], '');
            fs.writeFileSync('tmp/' + type + '.txt', lines.join('\n'), 'utf-8');
        }
    }

    private getDescriptionStr(structMap: any, indent: string): string[] {
        let lines: string[] = [];
        lines.push('{');
        for(let key in structMap) {
            let structValue = structMap[key];
            if(typeof(structValue) == 'object') {
                let subLines = this.getDescriptionStr(structValue, indent + '    ');
                lines.push(key + '?: {');
                for(let i = 1, len = subLines.length; i < len - 1; i++) {
                    lines.push(subLines[i]);
                }
                lines.push('}, ');
            } else {
                lines.push(key + '?: ' + structValue + ', ');
            }
        }
        lines.push('}, ');
        for(let i = 1, len = lines.length; i < len - 1; i++) {
            lines[i] = '    ' + lines[i];
        }
        return lines;
    }
}